#include "RegistroMF.h"
#include "util.h"
#include <cstring>
#include <iostream>
#include <stdexcept>
#include <sstream>
#include "../compresion/ppmcComp.h"
#include "../compresion/ppmcDescomp.h"

RegistroMF::RegistroMF() {}

RegistroMF::RegistroMF(istream &archivo)
{
	cargar(archivo);
}

RegistroMF::RegistroMF(const string titulo, const vector<string> autores,
				const string idioma, const string anio, const string letra)
			: titulo(titulo), autores(autores), idioma(idioma),
			 anio(anio), letra(letra) {}

RegistroMF::RegistroMF(const char* titulo, const vector<string> autores,
				const char* idioma, const char* anio, const char* letra)
			: titulo(titulo), autores(autores), idioma(idioma),
						 anio(anio), letra(letra) {}

void RegistroMF::cargar(istream &archivo)
{
	archivo.seekg(0,ios::beg);
	vector<string> acumulador;
	string tmp;
	getline(archivo, tmp);
	istringstream pLinea(tmp);
	while(getline(pLinea, tmp, '-'))
	{
		acumulador.push_back(tmp);
	}
	if (acumulador.size() == 3)
	{
		titulo = acumulador[1];
		idioma = acumulador[2];
	}
	else if (acumulador.size() == 4)
	{
		anio = acumulador[1];
		titulo = acumulador[2];
		idioma = acumulador[3];
	}
	else
	{
		throw std::runtime_error("Formato de archivo incorrecto.");
	}
	//Parseo de autores multiples
	istringstream subAut(acumulador[0]);
	while(getline(subAut, tmp, ';'))
	{
		normalizarYlimpiar(tmp);
		autores.push_back(tmp);
	}
	//Parseo del resto de la letra
	stringstream buffer;
	buffer << archivo.rdbuf();
	letra = buffer.str();
	normalizarYlimpiar(titulo);
	normalizarYlimpiar(anio);
	normalizarYlimpiar(letra);
}

void RegistroMF::cargar(const char* tiraBytes, const size_t tam)
{
	size_t contador = 0;
	aString(titulo, tiraBytes, M_TITULO, contador);
	size_t vecSize = 0;
	memcpy(&vecSize, tiraBytes+contador, M_VEC_AU);
	contador += M_VEC_AU;
	for (size_t i = 0; i < vecSize; ++i)
	{
		string aut;
		aString(aut, tiraBytes, M_AUTOR, contador);
		autores.push_back(aut);
	}
	aString(anio, tiraBytes, M_ANIO, contador);
	aString(idioma, tiraBytes, M_IDIOM, contador);
	aString(letra, tiraBytes, M_LETRA, contador);

	stringstream elComprimido;
	elComprimido.write(letra.c_str(), letra.size());
	stringstream elDescomprimido;
	ppmcDescomp descompresor(2, elComprimido, elDescomprimido, 0);
	letra = descompresor.descomprimir();
}

RegistroMF::RegistroMF(const char* tiraBytes, const size_t tam)
{
	cargar(tiraBytes,tam);
}

void RegistroMF::aString(string &destino, const char* origen, size_t preBytes, size_t &offset)
{
	size_t t = 0;
	memcpy(&t, origen+offset, preBytes);
	offset += preBytes;
	destino = string(origen+offset, t);
	offset += t;
}

void RegistroMF::memcpyPrefij(char* destino, const char* origen, size_t tam, size_t preBytes, size_t *offset)
{
	memcpy(destino+(*offset), &tam, preBytes);
	(*offset) += preBytes;
	memcpy(destino+(*offset), origen, tam);
	(*offset) += tam;
}

size_t RegistroMF::serializar(char *& buffer)
{
	RegistroMF copia(*this);

	 stringstream resultado;
	 ppmcComp compresor(2, resultado, 0);
	 copia.letra += (char)0;
	 compresor.comprimir(copia.letra);
	 copia.letra = resultado.str();

	size_t tam = copia.tamSerializado();
	buffer = new char[tam];
	size_t contador = 0;
	//Copio los datos
	memcpyPrefij(buffer, titulo.c_str(), titulo.length(), M_TITULO, &contador);

	//Numero de autores
	size_t numAut = autores.size();
	memcpy(buffer+contador, &numAut, M_VEC_AU);
	contador += M_VEC_AU;
	for(vector<string>::iterator it = autores.begin(); it != autores.end(); ++it)
	{
			memcpyPrefij(buffer, it->c_str(), it->length(), M_AUTOR, &contador);
	}
	memcpyPrefij(buffer, anio.c_str(), anio.length(), M_ANIO, &contador);
	memcpyPrefij(buffer, idioma.c_str(), idioma.length(), M_IDIOM, &contador);
	memcpyPrefij(buffer, copia.letra.c_str(), copia.letra.length(), M_LETRA, &contador);
	return contador;

	// La manera mas correcta de hacerlo queda sin aplicar por ahora
	/*stringstream stream;
	size_t aux = titulo.length();
	stream.write((char*)&aux, M_TITULO);
	stream << titulo;

	//Numero de autores
	for(vector<string>::iterator it = autores.begin(); it != autores.end(); ++it)
	{
		aux = it->length();
		stream.write((char*)&aux, M_AUTOR);
		stream << *it;
	}
	aux = anio.length();
	stream.write((char*)&aux, M_ANIO);
	stream << anio;
	aux = idioma.length();
	stream.write((char*)&aux, M_IDIOM);
	stream << idioma;

	streampos pos_init = stream.tellp();
	char relleno[M_LETRA] = {0};
	stream.write(relleno,M_LETRA);
	ppmcComp compresor(2, stream, pos_init+M_LETRA);
	string aCompr(letra);
	aCompr += 0;
	compresor.comprimir(aCompr);
	contador += (stream.tellp()-pos_init+M_LETRA);
	aux = letra.length();
	stream.write((char*)&aux, M_LETRA);
	stream << letra;

	size_t tam = stream.tellp();
	tam += 1;
	buffer = new char[tam];
	const char* result = stream.str().c_str();
	memcpy(buffer, result, tam);
	return tam;*/

}

size_t RegistroMF::tamSerializado()
{
	size_t tam = 0;
	tam += titulo.length() + M_TITULO;
	tam += M_VEC_AU;
	for(vector<string>::iterator it = autores.begin(); it != autores.end(); ++it)
	{
	    tam += it->length() + M_AUTOR;
	}
	tam += idioma.size() + M_IDIOM;
	tam += anio.size() + M_ANIO;
	tam += letra.size() + M_LETRA;
	return tam;
}

bool RegistroMF::igualdadParcial(const RegistroMF &otro)
{
	if (titulo != otro.titulo || autores.size() != otro.autores.size()
			|| idioma != otro.idioma) return false;
	for (size_t i = 0; i < autores.size(); ++i)
	{
		if (autores[i] != otro.autores[i]) return false;
	}
	return true;
}

bool operator==(const RegistroMF &A, const RegistroMF &B)
{
	if (A.titulo != B.titulo || A.autores.size() != B.autores.size() || A.anio != B.anio ||
			A.idioma != B.idioma || A.letra != B.letra) return false;
	for (size_t i = 0; i < A.autores.size(); ++i)
	{
		if (A.autores[i] != B.autores[i]) return false;
	}
	return true;
}

void RegistroMF::sCabecera(string &dest)
{
	dest.clear();
	dest.append(titulo);
	dest.append(" - ");
	for(vector<string>::const_iterator it = autores.begin(); it != autores.end(); ++it)
	{
		dest.append(*it);
		dest.append(",");
	}
	if (anio != "")
	{
		dest.append(" - ");
		dest.append(anio);
	}
	dest.append(" - ");
	dest.append(idioma);
}

ostream& operator<<(ostream &os, const RegistroMF &reg)
{
	os << "Titulo: " << reg.titulo << endl;
	os << "Autor(es): " << endl;
	for(vector<string>::const_iterator it = reg.autores.begin(); it != reg.autores.end(); ++it)
	{
		os << "\t" << *it << endl;
	}
	os << "Idioma: " << reg.idioma << endl;
	if (reg.anio != "") os << "Ano: " << reg.anio << endl;
	os << "Letra: " << endl;
	os << reg.letra;
    return os;
}
